# Consuming Variables

In Flowgram, managing and consuming variables is at the core of building dynamic, interactive applications. Understanding how to effectively consume variables is crucial for developers. This document will guide you through the various ways to consume variables in different scenarios.

## Getting the Variable Tree within a Node

In the nodes on the canvas, we often need to get the variables available in the current scope and display them in a tree structure for users to select and operate. The `useAvailableVariables` React Hook is designed for this purpose.

### `useAvailableVariables`

`useAvailableVariables` is a lightweight Hook that directly returns an array of available variables (`VariableDeclaration[]`) in the current scope. If you only need a simple list of variables and don't need to perform more complex operations, `useAvailableVariables` is a more convenient choice.

```tsx pure title="use-variable-tree.tsx"
import {
  type BaseVariableField,
  useAvailableVariables,
} from '@flowgram.ai/fixed-layout-editor';

// .... In a React component or Hook

const availableVariables = useAvailableVariables();

const renderVariable = (variable: BaseVariableField) => {
  // You can render each variable according to your needs here
  // ....
}

return availableVariables.map(renderVariable);

// ....
```

### Drilling Down into Object Type Variables

When a variable's type is `Object`, we often need to "drill down" into it to get its properties. The `ASTMatch.isObject` method can help us determine if a variable's type is an object. If it is, we can recursively render its `properties`.

```tsx pure title="use-variable-tree.tsx"
import {
  type BaseVariableField,
  ASTMatch,
} from '@flowgram.ai/fixed-layout-editor';

// ....

const renderVariable = (variable: BaseVariableField) => ({
  title: variable.meta?.title,
  key: variable.key,
  // Only Object type variables can be drilled down
  children: ASTMatch.isObject(variable.type) ? variable.type.properties.map(renderVariable) : [],
});

// ....

```

### Drilling Down into Array Type Variables

Similar to the `Object` type, when we encounter an `Array` type variable, we also want to display its internal structure. For arrays, we are usually concerned with the type of its elements. `ASTMatch.isArray` can determine if a variable's type is an array. It's worth noting that the element type of an array can be anything, even another array. Therefore, we need a recursive helper function `getTypeChildren` to handle this situation.

```tsx pure title="use-variable-tree.tsx"
import {
  type BaseVariableField,
  type BaseType,
  ASTMatch,
} from '@flowgram.ai/fixed-layout-editor';

// ....

const getTypeChildren = (type?: BaseType): BaseVariableField[] => {
  if (!type) return [];

  // Get the properties of an Object
  if (ASTMatch.isObject(type)) return type.properties;

  // Recursively get the element type of an Array
  if (ASTMatch.isArray(type)) return getTypeChildren(type.items);

  return [];
};

const renderVariable = (variable: BaseVariableField) => ({
  title: variable.meta?.title,
  key: variable.key,
  children: getTypeChildren(variable.type).map(renderVariable),
});

// ....

```

## Official Material: `VariableSelector`

To make it easier for you to integrate variable selection functionality into your application, we have prepared an official material for you - the `VariableSelector` component. It encapsulates all the logic mentioned earlier, allowing you to have a powerful and beautiful variable selector without starting from scratch.

<img loading="lazy" src="/materials/variable-selector.png" style={{width:500}}/>

`VariableSelector` not only supports displaying a variable tree but also has built-in advanced features such as search and filtering, which can greatly enhance the user experience. For a more detailed introduction, please refer to the [Official Form Materials](/guide/advanced/form-materials.html) documentation.

You can use it in the following two ways:

**1. By Referencing the NPM Package**

This is the simplest and most direct way. With just one line of code, you can introduce `VariableSelector` into your project.

```tsx
import { VariableSelector } from '@flowgram.ai/form-materials';
```

**2. By Copying the Source Code via CLI**

If you want to customize `VariableSelector` more deeply, we also provide a way to copy the component's source code directly into your project via the CLI. This way, you can modify it as you wish to meet your unique business needs.

```bash
npx @flowgram.ai/materials components/variable-selector
```

## `ScopeAvailableData`: Your Variable Toolbox

The `ScopeAvailableData` object is one of the core components of the variable system. It is returned by the `useScopeAvailable` Hook and is your main bridge for interacting with the available variables in the scope. You can think of it as a powerful "variable toolbox."

### `useScopeAvailable`

`useScopeAvailable` is a more powerful Hook that returns a `ScopeAvailableData` object, which not only contains all the available variable information in the current scope but also provides some advanced APIs, such as `trackByKeyPath`.

Its main differences from `useAvailableVariables` are:

*   **Return Value**: `useAvailableVariables` directly returns a variable array, while `useScopeAvailable` returns a `ScopeAvailableData` object that contains the `variables` property and other methods.
*   **Applicable Scenarios**: When you need to perform more complex operations on variables, such as tracking changes to a single variable, `useScopeAvailable` is your best choice.

```tsx
import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';

const available = useScopeAvailable();

// The available object contains the variable list and other APIs
console.log(available.variables);
```


### Getting the Variable List

The most basic usage is to get all the available variables in the current scope.

```tsx
import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';

function MyComponent() {
  const available = useScopeAvailable();

  // available.variables is an array containing all available variables
  console.log(available.variables);

  return (
    <ul>
      {available.variables.map(variable => (
        <li key={variable.key}>{variable.name}</li>
      ))}
    </ul>
  );
}
```

### Tracking Changes to a Single Variable: `trackByKeyPath`

When you are only concerned with the changes of a specific variable (especially one nested in an Object or Array), `trackByKeyPath` comes in handy. It allows you to accurately "subscribe" to the updates of this variable without causing the component to re-render due to changes in other unrelated variables, thus achieving finer performance optimization.

Suppose we have an Object type variable named `user` with a `name` property. We want to update the component when `user.name` changes.

```tsx
import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
import { useEffect, useState } from 'react';

function UserNameDisplay() {
  const available = useScopeAvailable();
  const [userName, setUserName] = useState('');

  useEffect(() => {
    // Define the variable path we want to track
    const keyPath = ['user', 'name'];

    // Start tracking!
    const disposable = available.trackByKeyPath(keyPath, (nameField) => {
      // This callback function will be triggered when user.name changes
      // nameField is the changed variable field, from which we can get the latest default value
      setUserName(nameField?.meta.default || '');
    });

    // When the component unmounts, don't forget to cancel the tracking to avoid memory leaks
    return () => disposable.dispose();
  }, [available]); // The dependency is the available object

  return <div>User Name: {userName}</div>;
}
```

### Advanced Event Listening API

In addition to `trackByKeyPath`, `ScopeAvailableData` also provides a set of lower-level event listening APIs that allow you to control the response logic to variable changes more finely. This is very useful when dealing with complex scenarios that require manual subscription management.

Let's use a table to compare these three core listening APIs in detail:

| API | Trigger | Callback Parameters | Core Differences and Applicable Scenarios |
| :--- | :--- | :--- | :--- |
| `onVariableListChange` | When the **list structure** of available variables changes. | `(variables: VariableDeclaration[]) => void` | **Only cares about the list itself**. For example, an upstream node adds/deletes an output variable, causing the total number or members of available variables to change. It does not care about internal or drilled-down changes to variables. Suitable for scenarios where the UI needs to be updated based on the presence or number of variables in the list. |
| `onAnyVariableChange` | When the **content (meta, type, drilldown fields)** of **any** variable in the list changes. | `(changedVariable: VariableDeclaration) => void` | **Only cares about content (type, meta, drilldown) updates to variables**. For example, a user modifies the type of an output variable. It does not care about changes to the list structure. Suitable for scenarios where you need to react to changes in the content of any variable. |
| `onListOrAnyVarChange` | When **either** of the above two situations occurs. | `(variables: VariableDeclaration[]) => void` | **The most comprehensive listener**, a combination of the previous two. It is triggered by either a change in the list structure or a change in the value of any variable. Suitable for "catch-all" scenarios where you need to respond to any possible changes. |

#### Code Example

Let's look at a specific example of how to use these APIs in a component.

```tsx
import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
import { useEffect } from 'react';

function AdvancedListenerComponent() {
  const available = useScopeAvailable();

  useEffect(() => {
    // 1. Listen for changes in the list structure
    const listChangeDisposable = available.onVariableListChange((variables) => {
      console.log('The structure of the available variable list has changed! The new list length is:', variables.length);
    });

    // 2. Listen for changes in the value of any variable
    const valueChangeDisposable = available.onAnyVariableChange((changedVariable) => {
      console.log(`The content of variable '${changedVariable.keyPath.join('.')}' has changed!`);
    });

    // 3. Listen for all changes (structure or content)
    const allChangesDisposable = available.onListOrAnyVarChange((variables) => {
      console.log('The variable list or the content of one of its variables has changed!');
      // Note: The callback parameter here is the complete variable list, not the single changed variable
    });

    // When the component unmounts, be sure to clean up all listeners to prevent memory leaks
    return () => {
      listChangeDisposable.dispose();
      valueChangeDisposable.dispose();
      allChangesDisposable.dispose();
    };
  }, [available]);

  return <div>Please check the console for logs of variable changes...</div>;
}
```

**Key Points**:

*   These APIs all return a `Disposable` object.
*   To avoid memory leaks and unnecessary calculations, you must call its `dispose()` method in the cleanup function of `useEffect` to cancel the listener.
